package com.kinyx.framework.reflect;

import java.util.function.Function;

import com.kinyx.framework.Configs;
import com.kinyx.framework.annotation.assist.CanNotNull;
import com.kinyx.framework.annotation.assist.CanNull;
import com.kinyx.framework.annotation.assist.ReturnNotNull;
import com.kinyx.framework.annotation.assist.ReturnNull;
import com.kinyx.framework.annotation.assist.Tester;
import com.kinyx.framework.annotation.assist.Throws;
import com.kinyx.framework.utils.KEnumUtils;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
 * 类型转换工具类
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class KTypeConverter {

	/**
	 * 添加转换器
	 * @param <Source> 源泛型
	 * @param <Target> 目标泛型
	 * @param sourceType 源类型
	 * @param targetType 目标类型
	 * @param converter 转换器
	 */
	public static <Source, Target> void addConverter(@CanNotNull final Class<Source> sourceType, @CanNotNull final Class<Target> targetType, @CanNotNull final Function<Source, Target> converter) {
		if ((sourceType == null) || (targetType == null) || (converter == null)) { throw new NullPointerException(); }
		TypeConverters.converters.put(sourceType, targetType, converter);
	}

	@ReturnNull("没有配置过该类型转换器")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(String, Param<?>...)", value = "ClassNotFoundException", when = "Class.forName(String)")
	private static Function<?, ?> getConverter(@CanNotNull final Class<?> sourceType, @CanNotNull final Class<?> targetType) {
		// 缓存过直接返回
		final Object converter = TypeConverters.converters.get(sourceType, targetType);
		if (converter != null) { return (Function<?, ?>) converter; }
		// 未缓存过则按照规则新建并加入缓存
		try {
			final String className = Configs.TypeConvertPackageName + "." + String.format("%s_%s", sourceType.getSimpleName(), targetType.getSimpleName());
			final Function<?, ?> func = KBeanUtils.newInstance(className);
			TypeConverters.converters.put(sourceType, targetType, func);
			return func;
		} catch (final Exception e) {
			return null;
		}
	}

	/**
	 * 将源对象转换为目标类型对象
	 * <pre>
	 * 判断源对象是否为null。
	 * 	源对象为null：判断目标类型是否为基本类型。
	 * 		目标类型是基本类型：返回基本类型的默认值。
	 * 		目标类型非基本类型：返回null。
	 * 	源对象非null：先从配置的类型转换器中取对应类型的转换器。（Configs.TypeConvertPackageName包中&#36;{from}_${to}）
	 * 		有该类型转换器：则使用该类型转换器进行转换。
	 * 		无该类型转换器：则判断目标类型是否为String类型。
	 * 			目标类型是String类型：则调用源对象的toString方法。
	 * 			目标类型非String类型：则判断源对象是否为枚举类型。
	 * 				源对象是枚举类型：则调判断目标类型是否为数字类型。
	 * 					目标类型是数字类型：则取源对象的ordinal值。
	 * 					目标类型非数字类型：则返回null值。
	 * 				源对象非枚举类型：则判断目标类型是否为枚举类型。
	 * 					目标类型是枚举类型：判断源对象是否为数字类型。
	 * 						源对象是数字类型：则调用源对象的ordinal方法匹配枚举值。
	 * 						源对象非数字类型：则调用源对象的toString方法匹配枚举值。
	 * 					目标类型非枚举类型：则通过KBeanUtils创建新对象，并复制同定义字段对应的值。
	 * </pre>
	 * @param <Target> 目标泛型
	 * @param sourceObj 源对象
	 * @param targetType 目标类型
	 * @return 目标类型对象
	 */
	@SuppressWarnings({
			"rawtypes", "unchecked"
	})
	@ReturnNull("sourceObj == null && targetType不是基本类型时")
	@ReturnNull("sourceObj != null && 对应类型的转换器返回null值")
	@ReturnNull("sourceObj != null && 目标类型时字符串类型 && 目标类型的toString()方法返回null值")
	@ReturnNull("sourceObj != null && 源对象为枚举类型 && 目标类型不是数字型")
	@ReturnNull("sourceObj != null && 目标类型为枚举类型 && 匹配不到枚举值")
	//	@Throws(at = "KFieldUtils.get(Object, Field)", value = "IllegalArgumentException", when = "Field.get(Object)")
	//	@Throws(at = "KFieldUtils.get(Object, Field)", value = "IllegalAccessException", when = "Field.get(Object)")
	//	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalArgumentException", when = "Field.set(Object, Object)")
	//	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalAccessException", when = "Field.set(Object, Object)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(String, Param<?>...)", value = "ClassNotFoundException", when = "Class.forName(String)")
	@Tester("com.kinyx.framework.reflect.KTypeConverter_Tester_convert")
	public static <Target> Target convert(@CanNull final Object sourceObj, @CanNotNull final Class<Target> targetType) {
		if (sourceObj == null) { return (Target) defaultValue(targetType); } // null值做默认值处理

		final Class<?> sourceType = sourceObj.getClass();// 经过调用，已经不可能为基本类型了。
		// 同样的，经过返回值，targetType即使设置了基本类型，也将自动装箱，因而此处直接做转换即可。

		// 有该类型的转换器定义，使用转换器。
		final Function converter = getConverter(sourceType, defaultBoxingType(targetType));
		if (converter != null) { return (Target) converter.apply(sourceObj); }

		// XXX转字符串类型
		if (targetType.equals(String.class)) { return (Target) sourceObj.toString(); }

		// 枚举类型转XXX
		if (sourceType.isEnum()) {
			switch (targetType.toString()) {
				// 转数字
				case "byte":
				case "java.lang.Byte":
				case "short":
				case "java.lang.Short":
				case "int":
				case "java.lang.Integer":
				case "long":
				case "java.lang.Long":
				case "float":
				case "java.lang.Float":
				case "double":
				case "java.lang.Double":
					return (Target) Integer.valueOf(KEnumUtils.ordinal(sourceObj));
				//其它类型暂不处理
				default :
					return null;
			}
		}

		// XXX转枚举类型
		if (targetType.isEnum()) {
			if (sourceObj instanceof Number) {// 数字类型转枚举
				return KEnumUtils.compare(targetType, ((Number) sourceObj).intValue());
			}
			// 其它类型转枚举
			return KEnumUtils.compare(targetType, e -> e.toString().equals(sourceObj.toString()));
		}

		// 新建实例
		final Target targetObj = KBeanUtils.newInstance(targetType);

		// 复制属性
		KBeanUtils.copyProperties(sourceObj, targetObj);

		// 返回对象
		return targetObj;
	}

	/**
	 * 取默认值
	 * @param clazz 类型
	 * @return 基本类型时返回基本类型默认值，其它类型返回null。
	 */
	@ReturnNull("非基本类型时返回null")
	@Tester("com.kinyx.framework.reflect.KTypeConverter_Tester_defaultValue")
	public static Object defaultValue(@CanNotNull final Class<?> clazz) {
		switch (clazz.toString()) {// 基本类型，取默认值。
			case "byte":
				return (byte) 0;
			case "short":
				return (short) 0;
			case "int":
				return 0;
			case "long":
				return 0L;
			case "float":
				return 0f;
			case "double":
				return 0d;
			case "char":
				return (char) 0;
			case "boolean":
				return false;
			default :// 非基本类型返回null值
				return null;
		}
	}

	/**
	 * 取基本类型对应的封装类型
	 * @param clazz 类型
	 * @return 基本类型时返回基本类型对象的封装类型，其它类型直接返回。
	 */
	@ReturnNotNull
	public static Class<?> defaultBoxingType(@CanNotNull final Class<?> clazz) {
		switch (clazz.toString()) {// 基本类型，取默认值。
			case "byte":
				return Byte.class;
			case "short":
				return Short.class;
			case "int":
				return Integer.class;
			case "long":
				return Long.class;
			case "float":
				return Float.class;
			case "double":
				return Double.class;
			case "char":
				return Character.class;
			case "boolean":
				return Boolean.class;
			default :// 非基本类型直接返回
				return clazz;
		}
	}

}
